SPDX-FileCopyrightText: 2025 ChloƩ Coppens & Lara Pietkowicz SPDX-FileCopyrightText: 2025 AlICe laboratory https://alicelab.be
SPDX-License-Identifier: GPL-3.0-or-later
import bpy
import bmesh
import pprint
import randomdef clean():
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete(use_global=False)
bpy.ops.outliner.orphans_purge()
clean()positions = []
partition = []
for _ in range(43):
positions.append(random.choice([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))
note = []
for _ in range(random.randint(1, 7)):
note.append(random.choice([1, 2, 3, 4, 5, 6, 7, 8, 9]))
partition.append(note)Ellen’s script data partition = [ [1, 5, 2, 7, 3], [4, 5, 8, 1, 9], [1, 5, 2, 7], [3, 1, 2], [3, 1, 2], [9], [1, 5, 2, 5], [8, 1, 9], [8, 1, 9, 1], [6, 3, 2, 7], [8, 1, 9], [8, 1, 9, 4], [1, 5, 4, 9], [1, 4, 6, 3], [1, 5, 4, 9], [1, 4, 6, 3], [1, 5, 4, 9], [2, 5], [6, 3, 2, 5], [6, 3, 2, 5], [1, 5, 2, 5], [9], [1, 4], [1, 4], [6, 3, 2, 5], [1, 5, 4, 9], [2, 7, 3], [7, 1, 1], [6], [6], [3], [3], [2, 7, 3], [6, 3, 2, 5], [6, 3, 2, 5], [5, 5, 3], [6, 3, 2, 5], [6, 3, 2, 5], [5, 5, 3], [6, 3, 2, 5], [6, 3, 2, 5], [8, 1, 9, 1], [4, 5, 8, 1, 9] ] positions = [5, 2, 7, 4, 4, 5, 7, 5, 3, 0, 2, 3, 5, 6, 7, 6, 5, 3, 2, 2, 2, 4, 3, 3, 3, 5, 4, 4, 3, 3, 3, 3, 3, 6, 6, 4, 6, 2, 2, 6, 6, 5, 2]
Braille data
braille_values = {
1: [0],
2: [0, 2],
3: [0, 1],
4: [0, 1, 3],
5: [0, 3],
6: [0, 1, 2],
7: [0, 1, 2, 3],
8: [0, 2, 3],
9: [1, 2],
}def coordinate_from_position(pos, n):
y = plane_distance if n % 2 == 0 else -plane_distance
print("In:", n, y, pos, end=" ")
return [n, y, pos * z_step]2.2 Generate plane surfaces 2.2.1 Left side
bpy.ops.mesh.primitive_plane_add(location=(30, -36.3, 28))
bpy.context.object.name = "gege-gauche"
bpy.ops.transform.resize(value=(38, 38, 38))
bpy.ops.transform.rotate(value=1.571, orient_axis="X")
bpy.ops.object.modifier_add(type="SOLIDIFY")
bpy.context.object.modifiers["Solidify"].thickness = 0.042.2.2 Right side
bpy.ops.mesh.primitive_plane_add(location=(30, 37.8, 28))
bpy.context.object.name = "gege-droite"
bpy.ops.transform.resize(value=(38, 38, 38))
bpy.ops.transform.rotate(value=1.571, orient_axis="X")
bpy.ops.object.modifier_add(type="SOLIDIFY")
bpy.context.object.modifiers["Solidify"].thickness = 0.042.3 Generate Braille’s pattern
def braille_sequence(coord, notes):
print("with patterns:")
note_sequence_dot_coordinates = []
for single_note in notes:
braille_pattern = braille_values[single_note]
print("braille:", braille_pattern)
brailles_dot_coords = braille(coord, braille_pattern)
note_sequence_dot_coordinates.append(brailles_dot_coords)
coord[0] += braille_steps
return note_sequence_dot_coordinates2.4 Braille’s parameters
sphere_radius = 0.35
braille_steps = 2
plane_distance = 76 / 2
braille_dot_offset = 0.5
z_step = 2.32.5 Generate Braille’s sphere according to pattern
def braille(coord, pattern):
new_coord = coord.copy()
dot_coordinates = []
for dot in pattern:
if dot == 0:
new_coord[0] -= braille_dot_offset
new_coord[2] += braille_dot_offsetmake a sphere and save coordinate
print("a sphere in", new_coord)
bpy.ops.mesh.primitive_uv_sphere_add(
radius=sphere_radius, location=new_coord
)
dot_coordinates.append(tuple(new_coord))reset coordinate
new_coord = coord.copy()
elif dot == 1:
new_coord[0] += braille_dot_offset
new_coord[2] += braille_dot_offsetmake a sphere and save coordinate
print("a sphere in", new_coord)
bpy.ops.mesh.primitive_uv_sphere_add(
radius=sphere_radius, location=new_coord
)
dot_coordinates.append(tuple(new_coord))reset coordinate
new_coord = coord.copy()
elif dot == 2:
new_coord[0] += braille_dot_offset
new_coord[2] -= braille_dot_offsetmake a sphere and save coordinate
print("a sphere in", new_coord)
bpy.ops.mesh.primitive_uv_sphere_add(
radius=sphere_radius, location=new_coord
)
dot_coordinates.append(tuple(new_coord))reset coordinate
new_coord = coord.copy()
elif dot == 3:
new_coord[0] = new_coord[0] - braille_dot_offset
new_coord[2] = new_coord[2] - braille_dot_offsetmake a sphere and save coordinate
print("a sphere in", new_coord)
bpy.ops.mesh.primitive_uv_sphere_add(
radius=sphere_radius, location=new_coord
)
dot_coordinates.append(tuple(new_coord))reset coordinate
new_coord = coord.copy()
return dot_coordinates2.6 Generate connections between notes 2.6.1 Generate dots according to the spheres
print("MAKE DOTS")
generated_dots = []
length = 0
for n, pos in enumerate(positions):
coord = coordinate_from_position(pos * 3, n)
coord[0] += length
notes = partition[n]
print("use", notes, end=" ")
braille_sequence_dots = braille_sequence(coord, notes)
length = len(braille_sequence_dots)
generated_dots.append(braille_sequence_dots)
pprint.pprint(generated_dots)2.6.2 Generate connections between line and dots
print("MAKE LINES")
n = 0
print("A Note:", generated_dots[n])
print("Next Note:", generated_dots[n + 1])
print("A Note's first braille:", generated_dots[n][0])
print("Next Note's first braille:", generated_dots[n + 1][0])
print("Next Note's first braille first dot:", generated_dots[n + 1][0][0])2.7 Generate a line
def make_line(point1, point2, obj_name="custom_line"):Make a line from two points. create a list of vertex coordinates
vert_coords = [point1, point2]create the mesh data
mesh_data = bpy.data.meshes.new(f"{obj_name}_data")create the mesh object using the mesh data
mesh_obj = bpy.data.objects.new(obj_name, mesh_data)add the mesh object into the scene
bpy.context.scene.collection.objects.link(mesh_obj)create a new bmesh
bm = bmesh.new()create and add a vertices
for coord in vert_coords:
bm.verts.new(coord)connect vertices into edge
bm.verts.ensure_lookup_table()
v1, v2 = bm.verts[0], bm.verts[1]
bm.edges.new((v1, v2))writes the bmesh data into the mesh data
bm.to_mesh(mesh_data)[Optional] update the mesh data (helps with redrawing the mesh in the viewport)
mesh_data.update()clean up/free memory that was allocated for the bmesh
bm.free()return object for later use
return bpy.data.objects[obj_name]def get_dot_coordinates(note):
dot_coord = []
for braille in note:
for dot in braille:
dot_coord.append(dot)
return dot_coorddef connect_brailles(note1, note2):
side1 = get_dot_coordinates(note1)
side2 = get_dot_coordinates(note2)
random.shuffle(side1)
random.shuffle(side2)
if len(side1) > len(side2):
reserve = side2.copy()
for n, dot1 in enumerate(side1):
if n < len(side2):
make_line(dot1, side2.pop())
else:
make_line(dot1, random.choice(reserve))
else:
for dot1 in side1:
reserve = side1.copy()
for n, dot2 in enumerate(side2):
if n < len(side1):
make_line(dot2, side1.pop())
else:
make_line(dot2, random.choice(reserve))2.8 Convert lines in mesh
note_number = len(generated_dots)
for n in range(note_number):
if n + 1 < note_number:
connect_brailles(generated_dots[n], generated_dots[n + 1])2.9 Convert mesh in tubes
for obj in bpy.data.objects:
if "line" in obj.name:
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
bpy.ops.object.convert(target="CURVE")
bpy.context.object.data.bevel_depth = 0.2
bpy.context.object.data.use_fill_caps = True